#include "Shape.h"
#include "glad/glad.h"
#include "m_camera.h"
#include <algorithm>
#include <iostream>
using namespace glm;
using namespace std;

IShape::IShape(vec3 color) : color(color) {}

void IShape::setColor(float r, float g, float b)
{
    color.x = r, color.y = g, color.z = b;
}

vector<_Vertex> Block::vertices;
unsigned int Block::VAO, Block::VBO;

void Block::init()
{
    static bool inited = false;
    if (inited)
        return;

    vec3 points[8] = {vec3(1, 1, 1),
                      vec3(-1, 1, 1),
                      vec3(-1, -1, 1),
                      vec3(1, -1, 1),
                      vec3(1, 1, -1),
                      vec3(-1, 1, -1),
                      vec3(-1, -1, -1),
                      vec3(1, -1, -1)};
    vec2 texcoord[4] = {vec2(1, 1),
                        vec2(0, 1),
                        vec2(0, 0),
                        vec2(1, 0)};
    int id_per_face[6][4] = {{1, 4, 8, 5},
                             {3, 2, 6, 7},
                             {2, 1, 5, 6},
                             {4, 3, 7, 8},
                             {2, 3, 4, 1},
                             {5, 8, 7, 6}};
    int index_per_face[6] = {0, 1, 3, 3, 1, 2};
    for (int i = 0; i < 6; i++)
    {
        vec3 nowface[6];
        for (int j = 0; j < 6; j++)
            nowface[j] = points[id_per_face[i][index_per_face[j]] - 1];
        vec3 normal(0.0f);
        for (int j = 0; j < 4; j++)
            normal += points[id_per_face[i][j] - 1];
        normal = normalize(normal);
        for (int j = 0; j < 6; j++)
            vertices.push_back(_Vertex(nowface[j], normal, texcoord[index_per_face[j]]));
    }

    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(_Vertex), &vertices[0], GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(_Vertex), (void *)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(_Vertex), (void *)offsetof(_Vertex, normal));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(_Vertex), (void *)offsetof(_Vertex, texCoords));
    glEnableVertexAttribArray(2);
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

Block::Block(float width, float height, float depth) : width(width),
                                                       height(height),
                                                       depth(depth) {}

Block::Block(const Block &block) : width(block.width),
                                   height(block.height),
                                   depth(block.depth),
                                   IShape(block.color) {}

void Block::setSize(float w, float h, float d)
{
    width = w;
    height = h;
    depth = d;
}
vec3 Block::getSize() const
{
    return vec3(width, height, depth);
}

void Block::setTexture(Texture texture)
{
    this->texture = texture;
}

void Block::setSpecTexture(Texture texture)
{
    this->spec_texture = texture;
}

void Block::draw(Shader shader, float x, float y, float z)
{
    shader.use();
    glEnable(GL_DEPTH_TEST);
    glUniform1i(shader.uniform("material.diffuse1"), 0);
    glUniform1i(shader.uniform("material.specular1"), 1);
    texture.active(GL_TEXTURE0);
    texture.bind();
    texture.active(GL_TEXTURE1);
    spec_texture.bind();
    glUniform3fv(shader.uniform("material.diffuse2"), 1, value_ptr(color));
    glUniform3fv(shader.uniform("material.specular2"), 1, value_ptr(vec3(0, 0, 0)));
    glUniform1f(shader.uniform("material.shininess"), 32.0);
    glUniform3fv(shader.uniform("dirLight.direction"), 1, value_ptr(vec3(0, -1, -1)));
    glUniform3fv(shader.uniform("dirLight.ambient"), 1, value_ptr(vec3(0.3, 0.3, 0.3)));
    glUniform3fv(shader.uniform("dirLight.diffuse"), 1, value_ptr(vec3(0.6, 0.6, 0.6)));
    glUniform3fv(shader.uniform("dirLight.specular"), 1, value_ptr(vec3(1.0, 1.0, 1.0)));

    Camera &camera = Camera::getinstance();
    mat4 model(1.0f);
    model = translate(model, vec3(x, y, z));
    model = scale(model, vec3(width / 2.0, height / 2.0, depth / 2.0));
    glUniformMatrix4fv(shader.uniform("model"), 1, GL_FALSE, value_ptr(model));
    glUniformMatrix4fv(shader.uniform("view"), 1, GL_FALSE, value_ptr(camera.view()));
    glUniformMatrix4fv(shader.uniform("projection"), 1, GL_FALSE, value_ptr(camera.projection()));
    glUniform3fv(shader.uniform("viewPos"), 1, value_ptr(camera.Position()));

    glBindVertexArray(Block::VAO);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    glBindVertexArray(0);
}

unsigned int Ball::VAO, Ball::VBO;
vector<_Vertex> Ball::vertices;
void Ball::init()
{
    static bool inited = false;
    if (inited)
        return;
    float r = 0.5;
    int xnum = 200, ynum = 100;
    for (int i = 0; i < ynum; i++)
    {
        for (int j = 0; j < xnum; j++)
        {
            float theta1 = radians(360.0 / xnum * j), theta2 = radians(360.0 / xnum * (j + 1));
            float phi1 = radians(180.0 / ynum * i), phi2 = radians(180.0 / ynum * (i + 1));
            if (i != ynum - 1)
            {
                vec3 pos1(sin(phi1) * cos(theta1), sin(phi1) * sin(theta1), cos(phi1));
                vec3 pos2(sin(phi2) * cos(theta1), sin(phi2) * sin(theta1), cos(phi2));
                vec3 pos3(sin(phi2) * cos(theta2), sin(phi2) * sin(theta2), cos(phi2));
                vertices.push_back(_Vertex(pos1, pos1));
                vertices.push_back(_Vertex(pos2, pos2));
                vertices.push_back(_Vertex(pos3, pos3));
            }
            if (i != 0)
            {
                vec3 pos1(sin(phi1) * cos(theta1), sin(phi1) * sin(theta1), cos(phi1));
                vec3 pos2(sin(phi2) * cos(theta2), sin(phi2) * sin(theta2), cos(phi2));
                vec3 pos3(sin(phi1) * cos(theta2), sin(phi1) * sin(theta2), cos(phi1));
                vec3 normal = normalize(cross(pos3 - pos1, pos2 - pos1));
                vertices.push_back(_Vertex(pos1, pos1));
                vertices.push_back(_Vertex(pos2, pos2));
                vertices.push_back(_Vertex(pos3, pos3));
            }
        }
    }
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(_Vertex), &vertices[0], GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(_Vertex), (void *)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(_Vertex), (void *)offsetof(_Vertex, normal));
    glEnableVertexAttribArray(1);
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}
Ball::Ball(float radius) : radius(radius) {}
Ball::Ball(const Ball &ball) : radius(ball.radius),
                               IShape(ball.color) {}

void Ball::draw(Shader shader, float x, float y, float z)
{
    shader.use();
    glEnable(GL_DEPTH_TEST);
    glUniform3fv(shader.uniform("material.diffuse"), 1, value_ptr(color));
    glUniform3fv(shader.uniform("material.specular"), 1, value_ptr(vec3(0.5, 0.5, 0.5)));
    glUniform1f(shader.uniform("material.shininess"), 32);

    glUniform3fv(shader.uniform("dirLight.direction"), 1, value_ptr(vec3(0, -1, -1)));
    glUniform3fv(shader.uniform("dirLight.ambient"), 1, value_ptr(vec3(0.2, 0.2, 0.2)));
    glUniform3fv(shader.uniform("dirLight.diffuse"), 1, value_ptr(vec3(0.5, 0.5, 0.5)));
    glUniform3fv(shader.uniform("dirLight.specular"), 1, value_ptr(vec3(1.0, 1.0, 1.0)));

    Camera &camera = Camera::getinstance();
    mat4 model(1.0f);
    model = translate(model, vec3(x, y, z));
    model = scale(model, vec3(radius));
    glUniformMatrix4fv(shader.uniform("model"), 1, GL_FALSE, value_ptr(model));
    glUniformMatrix4fv(shader.uniform("view"), 1, GL_FALSE, value_ptr(camera.view()));
    glUniformMatrix4fv(shader.uniform("projection"), 1, GL_FALSE, value_ptr(camera.projection()));
    glUniform3fv(shader.uniform("viewPos"), 1, value_ptr(camera.Position()));

    glBindVertexArray(Ball::VAO);
    glDrawArrays(GL_TRIANGLES, 0, Ball::vertices.size());
    glBindVertexArray(0);
}
void Ball::setSize(float r)
{
    radius = r;
}
float Ball::getSize() const
{
    return radius;
}

void shapeInit()
{
    Block::init();
    Ball::init();
}
int _sign(float x)
{
    if (x < 0)
        return -1;
    if (x == 0)
        return 0;
    return 1;
}
float _abs(float x)
{
    return x < 0 ? -x : x;
}
bool checkCollision(Block blk, vec3 posblk, Ball bal, vec3 posbal)
{
    vec3 blksize = blk.getSize();
    float r = bal.getSize();
    vec3 D = posbal - posblk;
    vec3 D_sign(_sign(D.x), _sign(D.y), _sign(D.z));
    D.x = std::min(_abs(D.x), blksize.x / 2) * D_sign.x;
    D.y = std::min(_abs(D.y), blksize.y / 2) * D_sign.y;
    D.z = std::min(_abs(D.z), blksize.z / 2) * D_sign.z;
    vec3 P = D + posblk;
    float len = glm::length(posbal - P);
    if (len <= r)
    {
        return true;
    }
    else
        return false;
}